﻿using Assets.Scripts.Bvh.Common;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;

namespace Assets.Scripts.Bvh.Cpu
{
    public class MortonCoder
    {
        public static List<RadixTreeNode> PrepareLeafNodes(List<Triangle> tris, AABB sceneAABB)
        {
            var leafNodes = new List<RadixTreeNode>();

            return leafNodes;
        }
        public static List<RadixTreeNode> PrepareData(List<AABB> aabbs, AABB sceneAABB)
        {
            var leafNodes = new List<RadixTreeNode>();
            for (int i = 0; i < aabbs.Count; i++)
            {
                var aabb = aabbs[i];
                var center = Normalize(aabb.center, sceneAABB);
                var code = MortonCoder.Morton3D(center);
                var node = new RadixTreeNode()
                {
                    aabb = aabb,
                    leftNodeIndex = -1,
                    mortonCode = code,
                    parentIndex = -1,
                    nodeIndex = i,
                    rightNodeIndex = -1,
                    triangle = aabb.triangle,
                };

                leafNodes.Add(node);
            }

            return leafNodes;
        }

        private static Vector3 Normalize(Vector3 center, AABB sceneAABB)
        {
            var min = sceneAABB.minCorner;
            var max = sceneAABB.maxCorner;

            //remap to [0,1]: x = (x - min) / (max - min);
            var x = (center.x - min.x) / (max.x - min.x);
            var y = (center.y - min.y) / (max.y - min.y);
            var z = (center.z - min.z) / (max.z - min.z);
            return new Vector3(x, y, z);
        }

        public static int ExpandBits(long v)
        {
            v = (v * 0x00010001u) & 0xFF0000FFu;
            v = (v * 0x00000101u) & 0x0F00F00Fu;
            v = (v * 0x00000011u) & 0xC30C30C3u;
            v = (v * 0x00000005u) & 0x49249249u;
            return (int)v;
        }

        public static int Morton3D(Vector3 value)
        {
            Assert.IsTrue(value.x <= 1.0f && value.x >= 0);
            Assert.IsTrue(value.y <= 1.0f && value.y >= 0);
            Assert.IsTrue(value.z <= 1.0f && value.z >= 0);

            var x = Mathf.Min(Mathf.Max(value.x * 1024.0f, 0.0f), 1023.0f);
            var y = Mathf.Min(Mathf.Max(value.y * 1024.0f, 0.0f), 1023.0f);
            var z = Mathf.Min(Mathf.Max(value.z * 1024.0f, 0.0f), 1023.0f);
            int xx = ExpandBits((int)x);
            int yy = ExpandBits((int)y);
            int zz = ExpandBits((int)z);
            return xx * 4 + yy * 2 + zz;
        }
    }
}
